<?php
/**
 * Created by PhpStorm.
 * Author: Shadow
 * Date: 2022/8/29
 * Time: 4:28 下午
 * description: ApiSafe.php
 * 接口安全及权限检测
 */

namespace app\adminapi\services;


use think\Exception;
use think\facade\Cache;

class ApiSafe
{

    /**
     * 请求超时时间
     * @var int
     */
    protected $timeout = 60;

    /**
     * 访问次数阀值
     * @var int
     */
    protected $times = 3;

    /**
     * 检测权限
     * @param $user_id
     * @throws Exception
     */
    public function checkAuth($user_id){
        # 获取当前登录用户的权限
        $cacheAuth = Cache::get('auth'.$user_id)?:[];
        if (count($cacheAuth) == 0) {
            throw new Exception("无权限访问",4003);
        }

        # 当前访问的权限
        $currentAuth = strtolower(request()->controller()).'/'.strtolower(request()->action());

        if (!in_array($currentAuth,$cacheAuth)) {
            throw new Exception("无权限访问",4003);
        }

    }

    /**
     * 时间戳机制
     * 客户端请求的时间戳（年月日）和服务器当前的时间戳（年月日）进行对比
     */
    public function checkTimestamp(){
        $serve_time = request()->time();// 服务器的时间

        // 判断客户端是否传递该参数
        if (!request()->has('timestamp')) {
            throw  new Exception("时间戳参数不正确",2001);
        }

        $client_time = request()->param('timestamp');// 客户端请求的时间
        // 两个时间进行对比
        if ($serve_time - $client_time > $this->timeout) {
            throw  new Exception("请求超时、请稍后重试",5001);
        }
    }

    /**
     * 签名机制
     * @1、接收所有请求的参数、剔除sign字段、以及文件流等字段、剔除空值
     * @2、根据键值进行排序（升序/倒序）
     * @3、按照一定规则进行拼接、示例：键=值 、通过"&"连接、生成待签名的字符串
     * @4、通过加密算法、生成签名(可以加盐)、示例：md5/sha1
     */
    public function checkSign(){
        $param = request()->param();
        if (!request()->has('sign')){
            throw new Exception("签名参数缺失",2001);
        }
        # 剔除sign字段
        $client_sign = $param['sign'];
        unset($param['sign']);
        # 剔除空值
        foreach ($param as $key => $value) {
            if (empty($value)) {
                unset($param[$key]);
            }
        }
        # 根据键值进行排序
        # krsort — 对数组按照键名逆向排序
        ksort($param);
        # 按照一定规则进行拼接
        $wait_sign = '';
        foreach ($param as $k=>$v) {
            $wait_sign .= $k . '='. $v .'&';
        }
        $wait_sign = ltrim($wait_sign,'&');

        $serve_sign = md5($wait_sign);

        // 服务端生成签名、和客户端进行对比
        if ($client_sign != $serve_sign) {
            throw new Exception("签名错误",2001);
        }
    }


    /**
     * 接口频率限制
     * @throws Exception
     */
    public function throttle(){
        $controller = request()->controller();
        $action = request()->action();
        $ip = request()->ip();
        $key = md5($controller.$action.$ip);
        # 判断是否访问过
        $request_times = cache($key);
        if (!$request_times) {
            // 第一次访问
            Cache::set($key,1,60);
        }elseif ($request_times >= $this->times) {
            throw new Exception("请求过于频繁、请稍后重试",4003);
        } else {
            Cache::inc($key);
        }

    }

}